package winusb

import (
	"errors"
	"fmt"
	"syscall"
	"unsafe"
)




type WinUsbApi struct {

	kernapi *syscall.DLL
	createEvent * syscall.Proc
	resetEvent  * syscall.Proc
	
	wusbapi *syscall.DLL
	winusb_Initialize *syscall.Proc
	winusb_Free *syscall.Proc
	winusb_QueryInterfaceSettings *syscall.Proc
	winusb_QueryPipe *syscall.Proc
	winusb_ControlTransfer *syscall.Proc
	winusb_WritePipe *syscall.Proc
	winusb_ReadPipe *syscall.Proc
	winusb_SetPipePolicy  *syscall.Proc
	winusb_GetOverlappedResult *syscall.Proc
}


func NewUsbApi()(*WinUsbApi, error) {

	var wupi WinUsbApi

	wdll, err := syscall.LoadDLL( "Winusb.dll" )
	if err != nil {
		return nil, err 
	}

	wupi.wusbapi = wdll

	wupi.winusb_Initialize, err = wdll.FindProc( "WinUsb_Initialize" )
	if err != nil {
		return nil, err
	}
	
	wupi.winusb_Free, err = wdll.FindProc( "WinUsb_Free" )
	if err != nil {
		return nil, err
	}

	wupi.winusb_ControlTransfer, err = wdll.FindProc( "WinUsb_ControlTransfer" )
	if err != nil {
		return nil, err
	}

	wupi.winusb_WritePipe, err = wdll.FindProc( "WinUsb_WritePipe" )
	if err != nil {
		return nil, err
	}

	wupi.winusb_ReadPipe, err = wdll.FindProc( "WinUsb_ReadPipe" )
	if err != nil {
		return nil, err
	}

	wupi.winusb_QueryInterfaceSettings, err = wdll.FindProc( "WinUsb_QueryInterfaceSettings" )
	if err != nil {
		return nil, err
	}

	wupi.winusb_QueryPipe, err = wdll.FindProc( "WinUsb_QueryPipe" )
	if err != nil {
		return nil, err
	}

	wupi.winusb_SetPipePolicy, err = wdll.FindProc( "WinUsb_SetPipePolicy" )
	if err != nil {
		return nil, err
	}

	wupi.winusb_GetOverlappedResult, err = wdll.FindProc( "WinUsb_GetOverlappedResult" )
	if err != nil {
		return nil, err
	}

	kdll, err := syscall.LoadDLL( "Kernel32.dll" )
	if err != nil {
		return nil, err
	}

	wupi.kernapi = kdll
	wupi.createEvent, err = kdll.FindProc( "CreateEventW" )
	if err != nil {
		return nil, err
	}

	wupi.resetEvent, err = kdll.FindProc( "ResetEvent" )
	if err != nil {
		return nil, err
	}

	return &wupi, nil
}




type UsbDevice struct {
	wupi *WinUsbApi

	hdl syscall.Handle
	uhdl syscall.Handle

	inbulk uint8
	outbulk uint8
	maxlen uint32

	// 多个并行 read 操作的 overlap 结构体.
	oidx int
	ovps []syscall.Overlapped
	odat [][512]byte
}


type winusb_INTERFACE_DESC struct  {
 	bLength uint8
   	bDescriptorType uint8
   	bInterfaceNumber uint8
   	bAlternateSetting uint8
   	bNumEndpoints uint8
   	bInterfaceClass uint8
   	bInterfaceSubClass uint8
   	bInterfaceProtocol uint8
   	iInterface uint8
}



/*
PipeType :
  0 - UsbdPipeTypeControl,
  1 - UsbdPipeTypeIsochronous,
  2 - UsbdPipeTypeBulk,
  3 - UsbdPipeTypeInterrupt
*/

type winusb_PIPE_INFO struct  {
	PipeType uint32
	PipeId uint8
	MaximumPacketSize uint16
	Interval uint8
}



/*
使用 createfile, winusb_Initialize 获得 usb device 句柄.
要求 usb 设备使用 winusb 驱动程序.
通过 winusb_QueryPipe 来检查 endp 类型.
目前主要为了跟 ft232h 交互, 所以检查原则是为 ft232h 定制.
必须有 2 个 endp, 而且都是 bulk 类型.
*/

func (wupi *WinUsbApi)OpenDevice( dpth string) (*UsbDevice, error) {
	
	var bret uintptr
	var udv UsbDevice
	var err error
	var uidesc winusb_INTERFACE_DESC
	var upinfo winusb_PIPE_INFO

	// 字符串转换成 utf16 格式.
	uary, _ := syscall.UTF16FromString( dpth )
	udv.hdl, err = syscall.CreateFile( &uary[0], syscall.GENERIC_READ | syscall.GENERIC_WRITE, syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE,
		nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL | syscall.FILE_FLAG_OVERLAPPED, 0 )

	if udv.hdl == syscall.InvalidHandle {
		return nil, err
	}
	
	// 获得 usb handle
	bret, _, err = wupi.winusb_Initialize.Call( uintptr(udv.hdl), uintptr( unsafe.Pointer( &(udv.uhdl) ) ) )
	if bret == 0 {
		syscall.Close( udv.hdl )
		fmt.Println( "usb init err" )
		return nil, err
	}

	//
	udv.wupi = wupi
	bret, _, err = wupi.winusb_QueryInterfaceSettings.Call( uintptr(udv.uhdl), 0, uintptr(unsafe.Pointer(&uidesc)) )
	if bret == 0 {
		udv.Close()
		return nil, err	
	}

	fmt.Println( " endpNum:", uidesc.bNumEndpoints )
	fmt.Println( "   Class:", uidesc.bInterfaceClass )
	fmt.Println( "subClass:", uidesc.bInterfaceSubClass )
	fmt.Println( "itfProto:", uidesc.bInterfaceProtocol )
	
	//
	if uidesc.bNumEndpoints != 2 {
		udv.Close()
		return nil, err
	}

	//
	bret, _, err = wupi.winusb_QueryPipe.Call( uintptr(udv.uhdl), 0, 0, uintptr(unsafe.Pointer(&upinfo)) )
	if bret == 0 {
		udv.Close()
		return nil, err
	}
	
	if upinfo.PipeType != 2 {
		udv.Close()
		return nil, errors.New( "pipe(0) not bulk" )
	}

	// pipeid = 0x81
	fmt.Println( "endp(0).PipeType:", upinfo.PipeType )
	fmt.Println( "  endp(0).PipeId:", upinfo.PipeId )
	fmt.Println( "  endp(0).MaxLen:", upinfo.MaximumPacketSize )
	udv.inbulk = upinfo.PipeId

	//
	bret, _, err = wupi.winusb_QueryPipe.Call( uintptr(udv.uhdl), 0, 1, uintptr(unsafe.Pointer(&upinfo)) )
	if bret == 0 {
		udv.Close()
		return nil, err
	}

	if upinfo.PipeType != 2 {
		udv.Close()
		return nil, errors.New( "pipe(1) not bulk" )
	}

	// pipeid = 0x2
	fmt.Println( "endp(1).PipeType:", upinfo.PipeType )
	fmt.Println( "  endp(1).PipeId:", upinfo.PipeId )
	fmt.Println( "  endp(1).MaxLen:", upinfo.MaximumPacketSize )
	udv.outbulk = upinfo.PipeId
	udv.maxlen = uint32(upinfo.MaximumPacketSize)
	return &udv, nil
}


/*
相比上面 OpenDevice 函数, 减少若干检查项目.
*/
func (wupi *WinUsbApi)OpenUsbDevice( dpth string) (*UsbDevice, error) {
	
	var bret uintptr
	var udv UsbDevice
	var err error
	var uidesc winusb_INTERFACE_DESC

	// 字符串转换成 utf16 格式.
	uary, _ := syscall.UTF16FromString( dpth )
	udv.hdl, err = syscall.CreateFile( &uary[0], syscall.GENERIC_READ | syscall.GENERIC_WRITE, syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE,
		nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL | syscall.FILE_FLAG_OVERLAPPED, 0 )

	if udv.hdl == syscall.InvalidHandle {
		return nil, err
	}
	
	// 获得 usb handle
	bret, _, err = wupi.winusb_Initialize.Call( uintptr(udv.hdl), uintptr( unsafe.Pointer( &(udv.uhdl) ) ) )
	if bret == 0 {
		syscall.Close( udv.hdl )
		fmt.Println( "usb init err" )
		return nil, err
	}

	//
	udv.wupi = wupi
	bret, _, err = wupi.winusb_QueryInterfaceSettings.Call( uintptr(udv.uhdl), 0, uintptr(unsafe.Pointer(&uidesc)) )
	if bret == 0 {
		udv.Close()
		return nil, err	
	}

	fmt.Println( " endpNum:", uidesc.bNumEndpoints )
	fmt.Println( "   Class:", uidesc.bInterfaceClass )
	fmt.Println( "subClass:", uidesc.bInterfaceSubClass )
	fmt.Println( "itfProto:", uidesc.bInterfaceProtocol )
	
	//
	udv.inbulk = 0xFF
	udv.outbulk = 0xFF
	udv.maxlen = 512
	return &udv, nil
}



func (udv *UsbDevice)Close() error {
	udv.wupi.winusb_Free.Call( uintptr(udv.uhdl) )
	syscall.Close( udv.hdl )
	return nil
}

/*

BOOL WinUsb_SetPipePolicy(
  [in] WINUSB_INTERFACE_HANDLE InterfaceHandle,
  [in] UCHAR                   PipeID,
  [in] ULONG                   PolicyType,
  [in] ULONG                   ValueLength,
  [in] PVOID                   Value
);


ValueLength 字段,  
	BOOL 和 DWORD ,   1字节 /  4字节.

// constants for WinUsb_Get/SetPipePolicy.

#define SHORT_PACKET_TERMINATE 	0x01		// BULK(OUT), 		BOOL
#define AUTO_CLEAR_STALL       	0x02		// BULK(IN),  		BOOL
#define PIPE_TRANSFER_TIMEOUT  	0x03		// BULK(OUT/IN), 	DWORD
#define IGNORE_SHORT_PACKETS   	0x04		// BULK(IN),  		BOOL
#define ALLOW_PARTIAL_READS    	0x05		// BULK(IN),  		BOOL
#define AUTO_FLUSH             	0x06		// BULK(IN),  		BOOL
#define RAW_IO                 	0x07		// BULK(IN),  		BOOL
#define MAXIMUM_TRANSFER_SIZE 	0x08		
#define RESET_PIPE_ON_RESUME 	0x09

*/

func (udv *UsbDevice)SetBulkInPolicy( idx int, uval uint32 ) error {

	var tsiz int
	var bval uint8
	var vptr uintptr

	switch idx {
	case 2,4,5,6,7:
		if uval != 0 {
			bval = 1
		} else {
			bval = 0
		}
		
		tsiz = 1
		vptr = uintptr( unsafe.Pointer(&bval) )

	case 3:
		tsiz = 4
		vptr = uintptr( unsafe.Pointer(&uval) )

	default:
		return errors.New("")
	}

	bret, _, err := udv.wupi.winusb_SetPipePolicy.Call( uintptr(udv.uhdl), uintptr(udv.inbulk), uintptr(idx), uintptr(tsiz), vptr )
	if bret == 0 {
		return err
	}

	return nil
}


func (udv *UsbDevice)SetBulkOutPolicy( idx int, uval uint32 ) error {
	var tsiz int
	var bval uint8
	var vptr uintptr

	switch idx {
	
	case 1:
		if uval != 0 {
			bval = 1
		} else {
			bval = 0
		}
		
		tsiz = 1
		vptr = uintptr( unsafe.Pointer(&bval) )

	case 3:

		tsiz = 4;
		vptr = uintptr( unsafe.Pointer(&uval) )

	default:
		return errors.New( "not support policy idx" )
	}

	bret, _, err := udv.wupi.winusb_SetPipePolicy.Call( uintptr(udv.uhdl), uintptr(udv.outbulk), uintptr(idx), uintptr(tsiz), vptr )
	if bret == 0 {
		return err
	}

	return nil
}



type setup_packet struct {
	bReqType uint8
	bRequest uint8
	wValue   uint16
	wIndex   uint16
	wLength  uint16
}



/*
*/
func (udv *UsbDevice)ControlVendorWrite( request uint8, wValue uint16, wIndex uint16, p []byte ) (int, error) {

	var cbtrans uint32
	var dptr uintptr
	var temp uint64

  	temp = uint64( len(p) )
  	temp = (temp << 16) | uint64( wIndex )
  	temp = (temp << 16) | uint64( wValue )
  	temp = (temp << 8) | uint64( request )
  	temp = (temp << 8) | uint64( 0x40 )

	if len(p) > 0  {
		dptr = uintptr( unsafe.Pointer( &p[0] ) )
	} else {
		dptr = 0
	}

	bret, _, err := udv.wupi.winusb_ControlTransfer.Call( uintptr(udv.uhdl), uintptr(temp), dptr,  uintptr(len(p)), uintptr(unsafe.Pointer(&cbtrans)), 0 )
	if bret == 0 {
		return 0, err
	}

	return int(cbtrans), nil
}


func (udv *UsbDevice)ControlVendorRead( request uint8, wValue uint16, wIndex uint16, p []byte ) (int, error) {
	
	var cbtrans uint32
	var dptr uintptr
	var temp uint64

  	temp = uint64( len(p) )
  	temp = (temp << 16) | uint64( wIndex )
  	temp = (temp << 16) | uint64( wValue )
  	temp = (temp << 8) | uint64( request )
  	temp = (temp << 8) | uint64( 0xC0 )

	dptr = uintptr( unsafe.Pointer( &p[0] ) )
	
	bret, _, err := udv.wupi.winusb_ControlTransfer.Call( uintptr(udv.uhdl), uintptr(temp), dptr,  uintptr(len(p)), uintptr(unsafe.Pointer(&cbtrans)), 0 )
	if bret == 0 {
		return 0, err
	}

	return int(cbtrans), nil	
}


func (udv *UsbDevice)BulkWrite( p []byte ) error {
	var bret uintptr
	var cbtrans uint32

	if len(p) > int(udv.maxlen)  {
		return errors.New( "send packet over endp max size")
	}

	bret, _, err := udv.wupi.winusb_WritePipe.Call( uintptr(udv.uhdl), uintptr(udv.outbulk), uintptr( unsafe.Pointer(&p[0]) ), uintptr(len(p)), uintptr(unsafe.Pointer(&cbtrans)), 0 )
	if bret == 0 {
		return err
	}

	if cbtrans != uint32(len(p))  {
		return errors.New( "WritePipe complete not equ packet size" )
	}

	return nil
}


func (udv *UsbDevice)BulkRead( p []byte ) (int, error)  {

	var bret uintptr
	var cbtrans uint32

	if len(p) < int(udv.maxlen)  {
		return 0, errors.New( "recv packet too little")
	}

	bret, _, err := udv.wupi.winusb_ReadPipe.Call( uintptr(udv.uhdl), uintptr(udv.inbulk), uintptr( unsafe.Pointer(&p[0]) ), uintptr(udv.maxlen), uintptr(unsafe.Pointer(&cbtrans)), 0 )
	if bret == 0 {
		return 0, err
	}

	return int(cbtrans), nil
}


/*
创建并行读操作需要的, 多个 overlap 结构体.
然后发起多个并行读操作.
*/
func (udv *UsbDevice)PrepareRead( cnt int ) error {

	udv.odat = make( [][512]byte, cnt )
	udv.ovps = make( []syscall.Overlapped, cnt )
	udv.oidx = 0

	for i:=0; i<cnt; i++ {
		
		// HANDLE CreateEventW( LPSECURITY_ATTRIBUTES lpAttributes, BOOL bManualReset, BOOL bInitialState, LPCWSTR lpName )
		hdl, _, err := udv.wupi.createEvent.Call( 0, 1, 0, 0 )
		if hdl == uintptr(syscall.InvalidHandle) {
			return err
		}

		udv.ovps[i].HEvent = syscall.Handle(hdl)
		udv.ovps[i].Internal = 0
		udv.ovps[i].InternalHigh = 0
		udv.ovps[i].Offset = 0
		udv.ovps[i].OffsetHigh = 0
	}

	/**/
	for i:=0; i<(cnt - 1); i++ {

		bret, _, err := udv.wupi.winusb_ReadPipe.Call( uintptr(udv.uhdl), uintptr(udv.inbulk), uintptr( unsafe.Pointer(&udv.odat[i][0]) ), 512, 0, uintptr( unsafe.Pointer(&udv.ovps[i]) ) )
		if bret == 0 {
			if err != syscall.ERROR_IO_PENDING {
				return err
			}
		}
	}

	return nil
}


func (udv *UsbDevice)ActualRead() ([]byte, error) {
	var cbtrans uint32

	cnt := len(udv.ovps)

	// BOOL WinUsb_GetOverlappedResult( WINUSB_INTERFACE_HANDLE InterfaceHandle, LPOVERLAPPED lpOverlapped, LPDWORD lpTransferred, BOOL bWait );
	bret, _, err := udv.wupi.winusb_GetOverlappedResult.Call( uintptr(udv.uhdl), uintptr( unsafe.Pointer(&udv.ovps[udv.oidx])), uintptr(unsafe.Pointer(&cbtrans)), 1 )
	if bret == 0 {
		return nil, err
	}

	//
	rult := udv.odat[udv.oidx][0: int(cbtrans)]

	// post next read
	nidx := (udv.oidx + cnt - 1) % cnt
	
	udv.wupi.resetEvent.Call( uintptr(udv.ovps[nidx].HEvent) )

	bret, _, err = udv.wupi.winusb_ReadPipe.Call( uintptr(udv.uhdl), uintptr(udv.inbulk), uintptr( unsafe.Pointer(&udv.odat[nidx][0]) ), 512, 0, uintptr( unsafe.Pointer(&udv.ovps[nidx])) )
	if bret == 0 {
		if err != syscall.ERROR_IO_PENDING  {
			return nil, err
		}
	}

	// 
	udv.oidx = ( udv.oidx + 1 ) % cnt
	return rult, nil
}



